package tash

import java.io.PrintStream
import scala.collection.JavaConverters._
import org.eclipse.jetty.http.MimeTypes
import org.eclipse.jetty.server.Request
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.handler.AbstractHandler
import org.eclipse.jetty.server.handler.HandlerList
import org.eclipse.jetty.server.handler.ResourceHandler
import org.eclipse.jetty.util.resource.Resource
import org.eclipse.jetty.websocket.WebSocket
import org.eclipse.jetty.websocket.WebSocket.Connection
import org.eclipse.jetty.websocket.WebSocketFactory
import org.slf4j.LoggerFactory
import org.apache.commons.io.IOUtils
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import scala.sys.process.Process

class TashServer {
  private val Log = LoggerFactory.getLogger(classOf[TashServer])

  class WSHandler extends WebSocket.OnTextMessage {
    private var conn: Connection = null

    override def onOpen(conn: Connection): Unit = {
      this.conn = conn
    }

    override def onClose(code: Int, message: String): Unit = {
      this.conn = null
    }

    override def onMessage(message: String): Unit = {
    }
  }

  class WSAcceptor extends WebSocketFactory.Acceptor {
    override def doWebSocketConnect(request: HttpServletRequest, protocol: String): WebSocket = {
      return new WSHandler()
    }

    override def checkOrigin(request: HttpServletRequest, origin: String): Boolean = {
      return true
    }
  }
  private val mWSFactory = new WebSocketFactory(new WSAcceptor())

  class WebSocketHandler extends AbstractHandler {
    private val Target = "/ws"

    override def handle(
        target: String,
        baseRequest: Request,
        request: HttpServletRequest,
        response: HttpServletResponse
    ): Unit = {
      if (target != Target) return
      baseRequest.setHandled(true)
      Log.info("Handling web-socket")
      if (!mWSFactory.acceptWebSocket(request,response)) {
        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Websocket only");
      }
    }
  }

  class LoggingHandler extends AbstractHandler {
    override def handle(
        target: String,
        baseRequest: Request,
        request: HttpServletRequest,
        response: HttpServletResponse
    ): Unit = {
      Log.info(
          "Received request: {}\nHeaders: {}\nCookies: {}",
          target,
          request.getHeaderNames.asScala
              .map { header => "%s : %s".format(header, request.getHeader(header)) }
              .mkString("\n"),
          request.getCookies)
    }
  }

  class CommandHandler extends AbstractHandler {
    private val Target = "/command"

    private def shell(command: String): String = {
      import scala.sys.process._
      return command !!
    }

    override def handle(
        target: String,
        baseRequest: Request,
        request: HttpServletRequest,
        response: HttpServletResponse
    ): Unit = {
      if (target != Target) return
      baseRequest.setHandled(true)
      if (baseRequest.getMethod != "POST") {
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST)
        return
      }
      val command = IOUtils.toString(baseRequest.getInputStream())
      Log.info("Executing command: '{}'", command)
      val stream = new PrintStream(response.getOutputStream())
      stream.printf("Processed command: '%s': %s", command, shell(command))
      stream.flush()
      response.setStatus(HttpServletResponse.SC_OK)
    }
  }

  def run() {
    val server = new Server(8000)
      val handlers = new HandlerList()
      handlers.addHandler(new LoggingHandler())
      handlers.addHandler(new WebSocketHandler())
      handlers.addHandler(new CommandHandler())

      {
        val resh = new ResourceHandler()
        resh.getMimeTypes().addMimeMapping("html", MimeTypes.TEXT_HTML_UTF_8)
        resh.getMimeTypes().addMimeMapping("js", "application/javascript")
        resh.getMimeTypes().addMimeMapping("dart", "application/dart")
        resh.setBaseResource(Resource.newClassPathResource("web"))
        resh.setAliases(true)
        handlers.addHandler(resh)
      }

      server.setHandler(handlers)
      server.start()
      server.join()
    }
}

object Tash {
  def main(args: Array[String]): Unit = {
    new TashServer().run()
  }
}
